package com.github.rxyor.redisson.client;

import java.util.Deque;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.TimeUnit;
import org.redisson.api.BatchOptions;
import org.redisson.api.ClusterNodesGroup;
import org.redisson.api.ExecutorOptions;
import org.redisson.api.LocalCachedMapOptions;
import org.redisson.api.MapOptions;
import org.redisson.api.Node;
import org.redisson.api.NodesGroup;
import org.redisson.api.RAtomicDouble;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RBatch;
import org.redisson.api.RBinaryStream;
import org.redisson.api.RBitSet;
import org.redisson.api.RBlockingDeque;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RBoundedBlockingQueue;
import org.redisson.api.RBucket;
import org.redisson.api.RBuckets;
import org.redisson.api.RCountDownLatch;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RDeque;
import org.redisson.api.RDoubleAdder;
import org.redisson.api.RGeo;
import org.redisson.api.RHyperLogLog;
import org.redisson.api.RKeys;
import org.redisson.api.RLexSortedSet;
import org.redisson.api.RList;
import org.redisson.api.RListMultimap;
import org.redisson.api.RListMultimapCache;
import org.redisson.api.RLiveObjectService;
import org.redisson.api.RLocalCachedMap;
import org.redisson.api.RLock;
import org.redisson.api.RLongAdder;
import org.redisson.api.RMap;
import org.redisson.api.RMapCache;
import org.redisson.api.RPatternTopic;
import org.redisson.api.RPermitExpirableSemaphore;
import org.redisson.api.RPriorityBlockingDeque;
import org.redisson.api.RPriorityBlockingQueue;
import org.redisson.api.RPriorityDeque;
import org.redisson.api.RPriorityQueue;
import org.redisson.api.RQueue;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RRemoteService;
import org.redisson.api.RRingBuffer;
import org.redisson.api.RScheduledExecutorService;
import org.redisson.api.RScoredSortedSet;
import org.redisson.api.RScript;
import org.redisson.api.RSemaphore;
import org.redisson.api.RSet;
import org.redisson.api.RSetCache;
import org.redisson.api.RSetMultimap;
import org.redisson.api.RSetMultimapCache;
import org.redisson.api.RSortedSet;
import org.redisson.api.RStream;
import org.redisson.api.RTopic;
import org.redisson.api.RTransaction;
import org.redisson.api.RedissonClient;
import org.redisson.api.TransactionOptions;
import org.redisson.client.codec.Codec;
import org.redisson.config.Config;

/**
 *<p>
 *
 *</p>
 *
 * @author liuyang
 * @date 2020/4/23 周四 10:06:00
 * @since 1.0.0
 */
public class CarpRedissonClientProxy implements CarpRedissonClient {

    private final String prefix;
    private final RedissonClient redissonClient;
    private final ThreadLocal<Deque<RLock>> holder = new ThreadLocal<>();

    public CarpRedissonClientProxy(String prefix, RedissonClient redissonClient) {
        this.prefix = prefix;
        this.redissonClient = redissonClient;
    }

    @SuppressWarnings("all")
    private Deque<RLock> getAndSetIfAbsentDeque() {
        Deque<RLock> rLockDeque = holder.get();
        if (rLockDeque != null) {
            return rLockDeque;
        }

        synchronized (Thread.currentThread()) {
            if (rLockDeque == null) {
                rLockDeque = new ConcurrentLinkedDeque<>();
                holder.set(rLockDeque);
            }
        }
        return rLockDeque;
    }

    @Override
    public void lockInterruptibly(String name, long leaseTime, TimeUnit unit) throws InterruptedException {
        RLock rLock = this.getLock(name);
        rLock.lockInterruptibly(leaseTime, unit);
        this.getAndSetIfAbsentDeque().add(rLock);
    }

    @Override
    public boolean tryLock(String name, long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
        RLock rLock = this.getLock(name);
        if (rLock.tryLock(waitTime, leaseTime, unit)) {
            this.getAndSetIfAbsentDeque().add(rLock);
            return true;
        }
        return false;
    }

    @Override
    public void lock(String name, long leaseTime, TimeUnit unit) {
        RLock rLock = this.getLock(name);
        rLock.lock(leaseTime, unit);
        this.getAndSetIfAbsentDeque().add(rLock);
    }

    @Override
    public void unlock() {
        Deque<RLock> deque = holder.get();
        if (deque == null) {
            return;
        }

        RLock rLock = deque.pollFirst();
        if (rLock != null && rLock.isLocked() && rLock.isHeldByCurrentThread()) {
            rLock.unlock();
        } else if (deque.isEmpty()) {
            holder.remove();
        }
    }

    @Override
    public <K, V> RStream<K, V> getStream(String name) {
        return redissonClient.getStream(prefix(name));
    }

    @Override
    public <K, V> RStream<K, V> getStream(String name, Codec codec) {
        return redissonClient.getStream(prefix(name), codec);
    }

    @Override
    public RRateLimiter getRateLimiter(String name) {
        return redissonClient.getRateLimiter(prefix(name));
    }

    @Override
    public RBinaryStream getBinaryStream(String name) {
        return redissonClient.getBinaryStream(prefix(name));
    }

    @Override
    public <V> RGeo<V> getGeo(String name) {
        return redissonClient.getGeo(prefix(name));
    }

    @Override
    public <V> RGeo<V> getGeo(String name, Codec codec) {
        return redissonClient.getGeo(prefix(name), codec);
    }

    @Override
    public <V> RSetCache<V> getSetCache(String name) {
        return redissonClient.getSetCache(prefix(name));
    }

    @Override
    public <V> RSetCache<V> getSetCache(String name, Codec codec) {
        return redissonClient.getSetCache(prefix(name), codec);
    }

    @Override
    public <K, V> RMapCache<K, V> getMapCache(String name, Codec codec) {
        return redissonClient.getMapCache(prefix(name), codec);

    }

    @Override
    public <K, V> RMapCache<K, V> getMapCache(String name, Codec codec, MapOptions<K, V> options) {
        return redissonClient.getMapCache(prefix(name), codec, options);
    }

    @Override
    public <K, V> RMapCache<K, V> getMapCache(String name) {
        return getMapCache(prefix(name));
    }

    @Override
    public <K, V> RMapCache<K, V> getMapCache(String name, MapOptions<K, V> options) {
        return redissonClient.getMapCache(prefix(name), options);
    }

    @Override
    public <V> RBucket<V> getBucket(String name) {
        return redissonClient.getBucket(prefix(name));
    }

    @Override
    public <V> RBucket<V> getBucket(String name, Codec codec) {
        return redissonClient.getBucket(prefix(name), codec);
    }

    @Override
    public RBuckets getBuckets() {
        return redissonClient.getBuckets();
    }

    @Override
    public RBuckets getBuckets(Codec codec) {
        return redissonClient.getBuckets(codec);
    }

    @Override
    public <V> RHyperLogLog<V> getHyperLogLog(String name) {
        return redissonClient.getHyperLogLog(prefix(name));
    }

    @Override
    public <V> RHyperLogLog<V> getHyperLogLog(String name, Codec codec) {
        return redissonClient.getHyperLogLog(prefix(name), codec);
    }

    @Override
    public <V> RList<V> getList(String name) {
        return redissonClient.getList(prefix(name));
    }

    @Override
    public <V> RList<V> getList(String name, Codec codec) {
        return redissonClient.getList(prefix(name), codec);
    }

    @Override
    public <K, V> RListMultimap<K, V> getListMultimap(String name) {
        return redissonClient.getListMultimap(prefix(name));
    }

    @Override
    public <K, V> RListMultimap<K, V> getListMultimap(String name, Codec codec) {
        return redissonClient.getListMultimap(prefix(name), codec);
    }

    @Override
    public <K, V> RListMultimapCache<K, V> getListMultimapCache(String name) {
        return redissonClient.getListMultimapCache(prefix(name));
    }

    @Override
    public <K, V> RListMultimapCache<K, V> getListMultimapCache(String name, Codec codec) {
        return redissonClient.getListMultimapCache(prefix(name), codec);
    }

    @Override
    public <K, V> RLocalCachedMap<K, V> getLocalCachedMap(String name, LocalCachedMapOptions<K, V> options) {
        return redissonClient.getLocalCachedMap(prefix(name), options);
    }

    @Override
    public <K, V> RLocalCachedMap<K, V> getLocalCachedMap(String name, Codec codec,
        LocalCachedMapOptions<K, V> options) {
        return redissonClient.getLocalCachedMap(prefix(name), codec, options);
    }

    @Override
    public <K, V> RMap<K, V> getMap(String name) {
        return redissonClient.getMap(prefix(name));
    }

    @Override
    public <K, V> RMap<K, V> getMap(String name, MapOptions<K, V> options) {
        return redissonClient.getMap(prefix(name), options);
    }

    @Override
    public <K, V> RMap<K, V> getMap(String name, Codec codec) {
        return redissonClient.getMap(prefix(name), codec);
    }

    @Override
    public <K, V> RMap<K, V> getMap(String name, Codec codec, MapOptions<K, V> options) {
        return redissonClient.getMap(prefix(name), codec, options);
    }

    @Override
    public <K, V> RSetMultimap<K, V> getSetMultimap(String name) {
        return redissonClient.getSetMultimap(prefix(name));
    }

    @Override
    public <K, V> RSetMultimap<K, V> getSetMultimap(String name, Codec codec) {
        return redissonClient.getSetMultimap(prefix(name), codec);
    }

    @Override
    public <K, V> RSetMultimapCache<K, V> getSetMultimapCache(String name) {
        return redissonClient.getSetMultimapCache(prefix(name));
    }

    @Override
    public <K, V> RSetMultimapCache<K, V> getSetMultimapCache(String name, Codec codec) {
        return redissonClient.getSetMultimapCache(prefix(name), codec);
    }

    @Override
    public RSemaphore getSemaphore(String name) {
        return redissonClient.getSemaphore(prefix(name));
    }

    @Override
    public RPermitExpirableSemaphore getPermitExpirableSemaphore(String name) {
        return redissonClient.getPermitExpirableSemaphore(prefix(name));
    }

    @Override
    public RLock getLock(String name) {
        return redissonClient.getLock(prefix(name));
    }

    @Override
    public RLock getMultiLock(RLock... locks) {
        return redissonClient.getMultiLock(locks);
    }

    @Override
    public RLock getRedLock(RLock... locks) {
        return redissonClient.getRedLock(locks);
    }

    @Override
    public RLock getFairLock(String name) {
        return redissonClient.getFairLock(prefix(name));
    }

    @Override
    public RReadWriteLock getReadWriteLock(String name) {
        return redissonClient.getReadWriteLock(prefix(name));
    }

    @Override
    public <V> RSet<V> getSet(String name) {
        return redissonClient.getSet(prefix(name));
    }

    @Override
    public <V> RSet<V> getSet(String name, Codec codec) {
        return redissonClient.getSet(prefix(name), codec);
    }

    @Override
    public <V> RSortedSet<V> getSortedSet(String name) {
        return redissonClient.getSortedSet(prefix(name));
    }

    @Override
    public <V> RSortedSet<V> getSortedSet(String name, Codec codec) {
        return redissonClient.getSortedSet(prefix(name), codec);
    }

    @Override
    public <V> RScoredSortedSet<V> getScoredSortedSet(String name) {
        return redissonClient.getScoredSortedSet(prefix(name));
    }

    @Override
    public <V> RScoredSortedSet<V> getScoredSortedSet(String name, Codec codec) {
        return redissonClient.getScoredSortedSet(prefix(name), codec);
    }

    @Override
    public RLexSortedSet getLexSortedSet(String name) {
        return redissonClient.getLexSortedSet(prefix(name));
    }

    @Override
    public RTopic getTopic(String name) {
        return redissonClient.getTopic(prefix(name));
    }

    @Override
    public RTopic getTopic(String name, Codec codec) {
        return redissonClient.getTopic(prefix(name), codec);
    }

    @Override
    public RPatternTopic getPatternTopic(String pattern) {
        return redissonClient.getPatternTopic(pattern);
    }

    @Override
    public RPatternTopic getPatternTopic(String pattern, Codec codec) {
        return redissonClient.getPatternTopic(pattern, codec);
    }

    @Override
    public <V> RQueue<V> getQueue(String name) {
        return redissonClient.getQueue(prefix(name));
    }

    @Override
    public <V> RDelayedQueue<V> getDelayedQueue(RQueue<V> destinationQueue) {
        return redissonClient.getDelayedQueue(destinationQueue);
    }

    @Override
    public <V> RQueue<V> getQueue(String name, Codec codec) {
        return null;
    }

    @Override
    public <V> RRingBuffer<V> getRingBuffer(String name) {
        return redissonClient.getRingBuffer(prefix(name));
    }

    @Override
    public <V> RRingBuffer<V> getRingBuffer(String name, Codec codec) {
        return redissonClient.getRingBuffer(prefix(name), codec);
    }

    @Override
    public <V> RPriorityQueue<V> getPriorityQueue(String name) {
        return redissonClient.getPriorityQueue(prefix(name));
    }

    @Override
    public <V> RPriorityQueue<V> getPriorityQueue(String name, Codec codec) {
        return redissonClient.getPriorityQueue(prefix(name), codec);
    }

    @Override
    public <V> RPriorityBlockingQueue<V> getPriorityBlockingQueue(String name) {
        return redissonClient.getPriorityBlockingQueue(prefix(name));
    }

    @Override
    public <V> RPriorityBlockingQueue<V> getPriorityBlockingQueue(String name, Codec codec) {
        return redissonClient.getPriorityBlockingQueue(prefix(name), codec);
    }

    @Override
    public <V> RPriorityBlockingDeque<V> getPriorityBlockingDeque(String name) {
        return redissonClient.getPriorityBlockingDeque(prefix(name));
    }

    @Override
    public <V> RPriorityBlockingDeque<V> getPriorityBlockingDeque(String name, Codec codec) {
        return redissonClient.getPriorityBlockingDeque(prefix(name), codec);
    }

    @Override
    public <V> RPriorityDeque<V> getPriorityDeque(String name) {
        return redissonClient.getPriorityDeque(prefix(name));
    }

    @Override
    public <V> RPriorityDeque<V> getPriorityDeque(String name, Codec codec) {
        return redissonClient.getPriorityDeque(prefix(name), codec);
    }

    @Override
    public <V> RBlockingQueue<V> getBlockingQueue(String name) {
        return redissonClient.getBlockingDeque(prefix(name));
    }

    @Override
    public <V> RBlockingQueue<V> getBlockingQueue(String name, Codec codec) {
        return redissonClient.getBlockingDeque(prefix(name), codec);
    }

    @Override
    public <V> RBoundedBlockingQueue<V> getBoundedBlockingQueue(String name) {
        return redissonClient.getBoundedBlockingQueue(prefix(name));
    }

    @Override
    public <V> RBoundedBlockingQueue<V> getBoundedBlockingQueue(String name, Codec codec) {
        return redissonClient.getBoundedBlockingQueue(prefix(name), codec);
    }

    @Override
    public <V> RDeque<V> getDeque(String name) {
        return redissonClient.getDeque(prefix(name));
    }

    @Override
    public <V> RDeque<V> getDeque(String name, Codec codec) {
        return redissonClient.getDeque(prefix(name), codec);
    }

    @Override
    public <V> RBlockingDeque<V> getBlockingDeque(String name) {
        return redissonClient.getBlockingDeque(prefix(name));
    }

    @Override
    public <V> RBlockingDeque<V> getBlockingDeque(String name, Codec codec) {
        return redissonClient.getBlockingDeque(prefix(name), codec);
    }

    @Override
    public RAtomicLong getAtomicLong(String name) {
        return redissonClient.getAtomicLong(prefix(name));
    }

    @Override
    public RAtomicDouble getAtomicDouble(String name) {
        return redissonClient.getAtomicDouble(prefix(name));
    }

    @Override
    public RLongAdder getLongAdder(String name) {
        return redissonClient.getLongAdder(prefix(name));
    }

    @Override
    public RDoubleAdder getDoubleAdder(String name) {
        return redissonClient.getDoubleAdder(prefix(name));
    }

    @Override
    public RCountDownLatch getCountDownLatch(String name) {
        return redissonClient.getCountDownLatch(prefix(name));
    }

    @Override
    public RBitSet getBitSet(String name) {
        return redissonClient.getBitSet(prefix(name));
    }

    @Override
    public <V> RBloomFilter<V> getBloomFilter(String name) {
        return redissonClient.getBloomFilter(prefix(name));
    }

    @Override
    public <V> RBloomFilter<V> getBloomFilter(String name, Codec codec) {
        return redissonClient.getBloomFilter(prefix(name), codec);
    }

    @Override
    public RScript getScript() {
        return redissonClient.getScript();
    }

    @Override
    public RScript getScript(Codec codec) {
        return redissonClient.getScript(codec);
    }

    @Override
    public RScheduledExecutorService getExecutorService(String name) {
        return redissonClient.getExecutorService(prefix(name));
    }

    @Override
    public RScheduledExecutorService getExecutorService(String name, ExecutorOptions options) {
        return redissonClient.getExecutorService(prefix(name), options);
    }

    @Deprecated
    @Override
    public RScheduledExecutorService getExecutorService(Codec codec, String name) {
        return redissonClient.getExecutorService(codec, prefix(name));
    }

    @Override
    public RScheduledExecutorService getExecutorService(String name, Codec codec) {
        return redissonClient.getExecutorService(prefix(name), codec);
    }

    @Override
    public RScheduledExecutorService getExecutorService(String name, Codec codec, ExecutorOptions options) {
        return redissonClient.getExecutorService(prefix(name), codec, options);
    }

    @Override
    public RRemoteService getRemoteService() {
        return redissonClient.getRemoteService();
    }

    @Override
    public RRemoteService getRemoteService(Codec codec) {
        return redissonClient.getRemoteService(codec);
    }

    @Override
    public RRemoteService getRemoteService(String name) {
        return redissonClient.getRemoteService(prefix(name));
    }

    @Override
    public RRemoteService getRemoteService(String name, Codec codec) {
        return redissonClient.getRemoteService(prefix(name), codec);
    }

    @Override
    public RTransaction createTransaction(TransactionOptions options) {
        return redissonClient.createTransaction(options);
    }

    @Override
    public RBatch createBatch(BatchOptions options) {
        return redissonClient.createBatch(options);
    }

    @Override
    public RBatch createBatch() {
        return redissonClient.createBatch();
    }

    @Override
    public RKeys getKeys() {
        return redissonClient.getKeys();
    }

    @Override
    public RLiveObjectService getLiveObjectService() {
        return redissonClient.getLiveObjectService();
    }

    @Override
    public void shutdown() {
        redissonClient.shutdown();
    }

    @Override
    public void shutdown(long quietPeriod, long timeout, TimeUnit unit) {
        redissonClient.shutdown(quietPeriod, timeout, unit);
    }

    @Override
    public Config getConfig() {
        return redissonClient.getConfig();
    }

    @Override
    public NodesGroup<Node> getNodesGroup() {
        return redissonClient.getNodesGroup();
    }

    @Override
    public ClusterNodesGroup getClusterNodesGroup() {
        return redissonClient.getClusterNodesGroup();
    }

    @Override
    public boolean isShutdown() {
        return redissonClient.isShutdown();
    }

    @Override
    public boolean isShuttingDown() {
        return redissonClient.isShuttingDown();
    }

    @Override
    public String getId() {
        return redissonClient.getId();
    }

    private String prefix(String name) {
        if (name == null || name.trim().length() == 0) {
            throw new IllegalArgumentException("name can't be blank");
        }

        if (prefix == null || prefix.trim().length() == 0) {
            return name;
        }

        return prefix + "::" + name;
    }
}
